﻿using Autofac;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Adapters;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.SystemTray.Services;
using GlobalHotKey;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
using Windows.Storage;

namespace FluentTerminal.SystemTray
{
    public static class Program
    {
        private const string MutexName = "FluentTerminalMutex";

        [STAThread]
        public static void Main(string[] args)
        {
            if (!Mutex.TryOpenExisting(MutexName, out _))
            {
                using (new Mutex(false, MutexName))
                {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);

                    JsonConvert.DefaultSettings = () =>
                    {
                        var settings = new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver(),
                        };
                        settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));

                        return settings;
                    };

                    var applicationDataContainers = new ApplicationDataContainers
                    {
                        LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
                        RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
                        KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
                        ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
                        Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always))
                    };

                    var containerBuilder = new ContainerBuilder();

                    containerBuilder.RegisterInstance(applicationDataContainers);
                    containerBuilder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
                    containerBuilder.RegisterType<TerminalsManager>().SingleInstance();
                    containerBuilder.RegisterType<ToggleWindowService>().SingleInstance();
                    containerBuilder.RegisterInstance(new HotKeyManager()).SingleInstance();
                    containerBuilder.RegisterType<SystemTrayApplicationContext>().SingleInstance();
                    containerBuilder.RegisterType<AppCommunicationService>().SingleInstance();
                    containerBuilder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>();
                    containerBuilder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
                    containerBuilder.RegisterType<UpdateService>().As<IUpdateService>().SingleInstance();
                    containerBuilder.RegisterInstance(Dispatcher.CurrentDispatcher).SingleInstance();

                    var container = containerBuilder.Build();

                    // TODO: It's maybe better to wait for logging to be configured before continuing?
                    // ReSharper disable once AssignmentIsFullyDiscarded
                    _ = ConfigureLoggingAsync();

                    var appCommunicationService = container.Resolve<AppCommunicationService>();

                    if (args.Length > 0 && args[2] == "appLaunched")
                    {
                        // ReSharper disable once AssignmentIsFullyDiscarded
                        _ = appCommunicationService.StartAppServiceConnectionAsync();
                    }

#if !STORE
                    // ReSharper disable once AssignmentIsFullyDiscarded
                    _ = Task.Factory.StartNew(() => container.Resolve<IUpdateService>().CheckForUpdateAsync());
#endif
                    var settingsService = container.Resolve<ISettingsService>();

                    // ReSharper disable once AssignmentIsFullyDiscarded
                    _ = Task.Factory.StartNew(() => Utilities.MuteTerminal(settingsService.GetApplicationSettings().MuteTerminalBeeps));

                    if (settingsService.GetApplicationSettings().EnableTrayIcon)
                    {
                        Application.Run(container.Resolve<SystemTrayApplicationContext>());
                    }
                    else
                    {
                        Application.Run();
                    }
                }
            }
            else
            {
                var eventWaitHandle = EventWaitHandle.OpenExisting(AppCommunicationService.EventWaitHandleName, System.Security.AccessControl.EventWaitHandleRights.Modify);
                eventWaitHandle.Set();
            }
        }

        private static async Task ConfigureLoggingAsync()
        {
            var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists).AsTask().ConfigureAwait(false);
            var logFile = Path.Combine(logDirectory.Path, "fluentterminal.systemtray.log");
            var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists).AsTask().ConfigureAwait(false);
            var configContent = await FileIO.ReadTextAsync(configFile).AsTask().ConfigureAwait(false);
            var config = JsonConvert.DeserializeObject<Logger.Configuration>(configContent) ?? new Logger.Configuration();
            Logger.Instance.Initialize(logFile, config);

            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;

            TaskScheduler.UnobservedTaskException += (sender, e) =>
                Logger.Instance.Error(e.Exception, "Unobserved Task Exception");
        }

        private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Logger.Instance.Error((Exception)e.ExceptionObject, "Unhandled Exception");
        }
    }
}